/*******************************************************************************
 * Copyright (c) 2000, 2005 IBM Corporation and others.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *     IBM Corporation - initial API and implementation
 *******************************************************************************/
package com.architexa.org.eclipse.gef.editpolicies;

import com.architexa.org.eclipse.gef.EditPart;
import com.architexa.org.eclipse.gef.Request;
import com.architexa.org.eclipse.gef.TreeEditPart;
import com.architexa.org.eclipse.gef.commands.Command;
import com.architexa.org.eclipse.gef.requests.ChangeBoundsRequest;
import com.architexa.org.eclipse.gef.requests.CreateRequest;
import com.architexa.org.eclipse.gef.requests.DropRequest;

import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.graphics.Rectangle;
import org.eclipse.swt.widgets.Tree;
import org.eclipse.swt.widgets.TreeItem;
import org.eclipse.swt.widgets.Widget;


/**
 * An EditPolicy for handling ADDS, MOVES, and CREATES on a {@link TreeEditPart}.
 * <P>
 * This EditPolicy is responsible for displaying the insertion feedback in the Tree during
 * the appropriate interactions.
 * <P>
 * This EditPolicy factors the {@link #getCommand(Request)} into three different abstract
 * methods which subclasses must implement.
 * @since 2.0
 */
public abstract class TreeContainerEditPolicy 
	extends AbstractEditPolicy 
{

private static TreeItem[] oldSelection;

/**
 * Returns a Command for adding the children to the container.
 * @param request the Request to add.
 * @return Command <code>null</code> or a Command to perform the add
 */
protected abstract Command getAddCommand(ChangeBoundsRequest request);

/**
 * Returns a Command for creating the object inside the container.
 * @param request the CreateRequest
 * @return Command <code>null</code> or a Command to perform the create
 */
protected abstract Command getCreateCommand(CreateRequest request);

/**
 * Returns a Command for moving the children within the container.
 * @param request the Request to move
 * @return Command <code>null</code> or a Command to perform the move
 */
protected abstract Command getMoveChildrenCommand(ChangeBoundsRequest request);

private void eraseDropFeedback(Request req) {
	getTree().setInsertMark(null, true);
	restoreSelection();
}

/**
 * @see com.architexa.org.eclipse.gef.EditPolicy#eraseTargetFeedback(Request)
 */
public void eraseTargetFeedback(Request req) {
	if (req.getType().equals(REQ_MOVE)
		|| req.getType().equals(REQ_ADD)
		|| req.getType().equals(REQ_CREATE))
		eraseDropFeedback(req);
}

/**
 * Calculates the index of the TreeItem at given point.
 * @param pt the Point in the Viewer
 * @return the index of the TreeItem
 */
protected final int findIndexOfTreeItemAt(com.architexa.org.eclipse.draw2d.geometry.Point pt) {
	int index = -1;
	TreeItem item = findTreeItemAt(pt);	
	if (item != null) {
		index = getHost().getChildren().indexOf(item.getData());	
		if (index >= 0 && !isInUpperHalf(item.getBounds(), pt))
			index++;
	}
	return index;
}

/**
 * Calculates the <code>TreeItem</code> at a specified {@link
 * com.architexa.org.eclipse.draw2d.geometry.Point}.
 * @param pt the draw2d Point
 * @return <code>null</code> or the TreeItem
 */
protected final TreeItem findTreeItemAt(com.architexa.org.eclipse.draw2d.geometry.Point pt) {
	return getTree().getItem(new Point(pt.x, pt.y));			
}

/**
 * @see com.architexa.org.eclipse.gef.EditPolicy#getCommand(Request)
 */
public Command getCommand(Request req) {
	if (req.getType().equals(REQ_MOVE_CHILDREN))
		return getMoveChildrenCommand((ChangeBoundsRequest)req);
	if (req.getType().equals(REQ_ADD))
		return getAddCommand((ChangeBoundsRequest)req);
	if (req.getType().equals(REQ_CREATE))
		return getCreateCommand((CreateRequest)req);
		
	return null;
}

/**
 * Returns the host EditPart when appropriate. Targeting is done by checking if the mouse
 * is clearly over the host's TreeItem.
 * @see com.architexa.org.eclipse.gef.EditPolicy#getTargetEditPart(Request)
 */
public EditPart getTargetEditPart(Request req) {
	if (req.getType().equals(REQ_ADD)
	  || req.getType().equals(REQ_MOVE)
	  || req.getType().equals(REQ_CREATE)) {
		DropRequest drop = (DropRequest) req;
		Point where = new Point(drop.getLocation().x, drop.getLocation().y);
		Widget widget = ((TreeEditPart) getHost()).getWidget();
		if (widget instanceof Tree)
			return getHost();
		TreeItem treeitem = (TreeItem) widget;
		Rectangle bounds = treeitem.getBounds();
		int fudge = bounds.height / 5;
		Rectangle inner =
			new Rectangle(
				bounds.x,
				bounds.y + fudge,
				bounds.width,
				bounds.height - (treeitem.getExpanded() ? 0 : fudge * 2));
		//Point is either outside the Treeitem, or inside the inner Rect.
		if (!bounds.contains(where) || inner.contains(where))
			return getHost();
	}
	return null;
}

private Tree getTree() {
	Widget widget = ((TreeEditPart)getHost()).getWidget();
	if (widget instanceof Tree)
		return (Tree)widget;
	else
		return ((TreeItem)widget).getParent();
}

private void insertMarkAfterLastChild(TreeItem[] children) {
	if (children != null && children.length > 0) {
		TreeItem item = children[ children.length - 1 ];
		getTree().setInsertMark(item, false);
	}
}

private boolean isInUpperHalf(Rectangle rect, 
					   com.architexa.org.eclipse.draw2d.geometry.Point pt) {
	Rectangle tempRect = new Rectangle(rect.x, rect.y, 
							rect.width, rect.height / 2);
	return tempRect.contains(new Point(pt.x, pt.y));
}

private void restoreSelection() {
	if (oldSelection != null) {
		try {
			getTree().setSelection(oldSelection);
		} finally {
			oldSelection = null;
		}
	}
}

private void showDropFeedback(DropRequest request) {
	Widget hostWidget = ((TreeEditPart)getHost()).getWidget();
	Tree tree = getTree();

	if (oldSelection == null) {
		oldSelection = tree.getSelection();
		TreeItem newSelection[];
		if (hostWidget instanceof Tree)
			newSelection = new TreeItem[0];
		else
			newSelection = new TreeItem[]{(TreeItem)hostWidget};
		tree.setSelection(newSelection);
	}
	
	com.architexa.org.eclipse.draw2d.geometry.Point pt = request.getLocation();
	TreeItem item = findTreeItemAt(pt);
	if (item == null) {
		if (hostWidget == tree) {
			insertMarkAfterLastChild(tree.getItems());
		}
	} else if (item == hostWidget) {
		tree.setInsertMark(null, true);
	} else {
		boolean before = isInUpperHalf(item.getBounds(), pt);
		tree.setInsertMark(item, before);
	}
}

/**
 * @see com.architexa.org.eclipse.gef.EditPolicy#showTargetFeedback(Request)
 */
public void showTargetFeedback(Request req) {
	if (req.getType().equals(REQ_MOVE)
		|| req.getType().equals(REQ_ADD)
		|| req.getType().equals(REQ_CREATE))
		showDropFeedback((DropRequest)req);   			
}

}
